package com.shelpe.services.auth.controller;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.math.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
import org.springframework.security.oauth2.common.exceptions.UnauthorizedUserException;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.NoSuchClientException;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
import org.springframework.stereotype.Controller;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.shelpe.services.auth.entity.AuthInfo;
import com.shelpe.services.auth.entity.Users;
import com.shelpe.services.auth.repository.AuthRepository;
import com.shelpe.services.auth.repository.UsersRepository;
import com.shelpe.services.auth.tokens.InternalAuthenticationToken;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;

@Controller
@RequestMapping("/authservice")
@Slf4j
public class PHPSimbaController {
	
	@Autowired
	private TokenEndpoint tokenEndpoint;
	@Autowired
	private JdbcClientDetailsService clientDetailsService;
	@Autowired
	private AuthRepository authRepository;
	@Autowired
	private UsersRepository usersRepository;
	@Value("${oauth.token.password.default:shelpe_sinbad}")
	private String defaultPassword;
	@Value("${oauth.php.token:sh2015sh}")
	private String phpToken;
	
	@ResponseStatus(HttpStatus.UNAUTHORIZED)
	@ExceptionHandler(UnauthorizedUserException.class)
	@ResponseBody
	public Response handleUnauthorizedUserException(UnauthorizedUserException ex){
		Response res = new Response();
		res.setStatus(false);
		res.setMessage(ex.getMessage());
		return res;
	}
	
	@ResponseStatus(HttpStatus.UNAUTHORIZED)
	@ExceptionHandler(InvalidClientException.class)
	@ResponseBody
	public Response handleInvalidClientException(InvalidClientException ex){
		Response res = new Response();
		res.setStatus(false);
		res.setMessage(ex.getMessage());
		return res;
	}

	@RequestMapping(path = "/tokens/{user_id}", method = RequestMethod.GET)
	public ResponseEntity<OAuth2AccessToken> token(@PathVariable("user_id") long userID, 
			@RequestParam(name="token")String token) throws HttpRequestMethodNotSupportedException{
		if(token == null || !token.equals(this.phpToken)){
			throw new UnauthorizedUserException("invalid_app_token");
		}
		
		ClientDetails detail = this.loadClientByClientId(userID);
		
		if(detail == null){
			log.warn("client detail is not found, try to fetch user info from taobao oauth:{}", userID);
			AuthInfo info = this.authRepository.findByTaobaoUserId(userID + "");
			if(info != null){
				BaseClientDetails clientDetails = new BaseClientDetails();
				clientDetails.setClientId(userID + "");
				clientDetails.setClientSecret(defaultPassword);
				this.fillupClientDetails(clientDetails);
				clientDetails.addAdditionalInformation("nick", info.getTaobaoUserNick());
				this.clientDetailsService.addClientDetails(clientDetails);
			}else{
				log.info("taobao oauth is not found: {}, maybe he is", userID);
				Users user = this.usersRepository.findTopByUserId(userID);
				if(user != null){
					BaseClientDetails clientDetails = new BaseClientDetails();
					clientDetails.setClientId(userID + "");
					clientDetails.setClientSecret(defaultPassword);
					this.fillupClientDetails(clientDetails);
					clientDetails.addAdditionalInformation("nick", user.getNick());
					this.clientDetailsService.addClientDetails(clientDetails);
				}else{
					log.info("taobao user is not found: {}", userID);
				}
			}
			detail = this.loadClientByClientId(userID);
		}
		
		if(detail == null){
			throw new InvalidClientException("client_detail_not_found");
		}
		
		String nickName = "";
		if( detail.getAdditionalInformation()!=null && detail.getAdditionalInformation().get("nick") != null){
			nickName = detail.getAdditionalInformation().get("nick").toString();
		}
		Map<String, String> parameters = new HashMap<String, String>();
		parameters.put("tenant_name", nickName);
		parameters.put(OAuth2Utils.GRANT_TYPE, "client_credentials");
		parameters.put(OAuth2Utils.CLIENT_ID, detail.getClientId());
		
		InternalAuthenticationToken principal = new InternalAuthenticationToken(detail.getClientId(), token, null);
		principal.setAuthenticated(true);
		
		return this.tokenEndpoint.postAccessToken(principal, parameters);
	}
	
	@RequestMapping(path = "/tokens", method = RequestMethod.POST)
	@ResponseBody
	public Response createClientDetail(@RequestBody AuthUser auth){
		Response res = new Response();
		
		if(auth == null || auth.getUserID() == 0){
			res.setStatus(false);
			return res;
		}
		
		ClientDetails detail = this.loadClientByClientId(auth.getUserID());
		if(detail != null){
			this.clientDetailsService.updateClientSecret(auth.getUserID() + "", defaultPassword);
		}else{
			BaseClientDetails clientDetails = new BaseClientDetails();
			clientDetails.setClientId(auth.getUserID() + "");
			clientDetails.setClientSecret(defaultPassword);
			this.fillupClientDetails(clientDetails);
			clientDetails.addAdditionalInformation("nick", auth.getNickName());
			this.clientDetailsService.addClientDetails(clientDetails);
		}
		res.setStatus(true);
		return res;
	}
	
	@RequestMapping(path = "/tokens/@sync", method = RequestMethod.GET)
	@ResponseBody
	public Response syncClientDetails(){
		Response res = new Response();
		
		Iterable<AuthInfo> authInfos = this.authRepository.findAll();
		Iterator<AuthInfo> it = authInfos.iterator();
		while(it.hasNext()){
			AuthInfo info = it.next();
			long userID = NumberUtils.toLong(info.getTaobaoUserId());
			if(userID > 0){
				ClientDetails detail = this.loadClientByClientId(userID);
				if(detail == null){
					BaseClientDetails clientDetails = new BaseClientDetails();
					clientDetails.setClientId(userID + "");
					clientDetails.setClientSecret(defaultPassword);
					this.fillupClientDetails(clientDetails);
					clientDetails.addAdditionalInformation("nick", info.getTaobaoUserNick());
					log.debug("sync oauth: {}", info);
				}else{
					log.debug("oauth has been synced: {}", info);
				}
			}else{
				log.error("invalid user ID: {}", info);
			}
		}
		
		res.setStatus(true);
		return res;
	}
	
	private void fillupClientDetails(BaseClientDetails clientDetails){
		List<String> authorizedGrantTypes = new ArrayList<String>();
		authorizedGrantTypes.add("authorization_code");
		authorizedGrantTypes.add("refresh_token");
		authorizedGrantTypes.add("client_credentials");
		authorizedGrantTypes.add("password");
		clientDetails.setAuthorizedGrantTypes(authorizedGrantTypes);
		List<String> scopes = new ArrayList<String>();
		scopes.add("openid");
		clientDetails.setScope(scopes);
	}
	
	private ClientDetails loadClientByClientId(long userID){
		ClientDetails detail = null;
		try{
			detail = this.clientDetailsService.loadClientByClientId(userID + "");
		}catch(NoSuchClientException e){
			
		}
		return detail;
	}
	
	@Data
	public static class AuthUser{
		@JsonProperty("user_id")
		private long userID;
		@JsonProperty("nick")
		private String nickName;
		@JsonProperty("token")
		private String accessToken;
	}
	
	@Data
	@JsonInclude(Include.NON_NULL)
	public static class Response{
		private boolean status;
		private String message;
	}
	
}
